/*
 * Copyright (c) 2009, Katholieke Universiteit Leuven
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *     * Neither the name of the Katholieke Universiteit Leuven nor the names of
 *       its contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/**
 * @addtogroup cstore
 * @{
 */
/**
 * @internal
 * @file 
 * Implementation of the LooCI Component Store
 * @author 
 * Wouter Horré <wouter.horre@cs.kuleuven.be>
 */

#include "contiki.h"
#include "cfs.h"
#include "cfs-coffee.h"
#include "contiki-net.h"
#include "componentstore_private.h"
#include "elfloader.h"
#include "components/components_private.h"
#include "components/components.h"
#include "events/events_private.h"

#ifdef LOOCI_CSTORE_DEBUG
#include <stdio.h>
#ifdef CONTIKI_TARGET_AVR_RAVEN
#include <avr/pgmspace.h>
#define PRINTF(FORMAT,args...) printf_P(PSTR(FORMAT),##args)
#else
#define PRINTF printf
#endif // CONTIKI_TARGET_AVR_RAVEN
#else
#define PRINTF(...)
#endif

#ifndef LOOCI_CSTORE_PORT
#define LOOCI_CSTORE_PORT 4321
#endif

#ifndef LOOCI_CSTORE_MAX_CONN
#define LOOCI_CSTORE_MAX_CONN 1
#endif

PROCESS(looci_component_store, "LooCI Component Store");

#define CSTORE_BUF_NONE 0
#define CSTORE_BUF_READ 1
#define CSTORE_BUF_BLOCKED_NEWDATA 2

#define TMP_FILE "tempfile"

struct cstore_buf {
  u8_t * readptr;
  u16_t readlen;
  u8_t state;
};

struct cstore_conn_state {
  struct pt pt;
  struct cstore_buf buffer;
  u16_t length;
  u16_t received;
  int fp;
  struct component ** infoc;
};

MEMB(states, struct cstore_conn_state, LOOCI_CSTORE_MAX_CONN);

static struct component ** componentgroups[ELFLOADER_SLOTS];

static void add_components(struct component * const * components) {
  int i;
  PRINTF("Adding components to component repository\r\n");
  for(i=0; components[i] != NULL; ++i) {
    PRINTF("Adding a component\r\n");
    components_add(components[i]);
  }
}

/**
 * @internal
 * Load components from the given file descriptor
 *
 * @param fd The file descriptor to load from
 * 
 * @return the elfloader slot where the components are loaded to
 */
static uint8_t cstore_load(char * filename) {
  uint8_t slot = ELFLOADER_SLOTS; // this is an invalid slot number.
  int fd = cfs_open(filename, CFS_READ | CFS_WRITE);

  if(fd >= 0) {
    int ret = elfloader_load(fd, &slot);
    cfs_close(fd);
    if(ret == ELFLOADER_OK) {
      PRINTF("Successfully loaded components from file %s\r\n", filename);
      PRINTF("Adding components from %p\r\n", elfloader_autostart_processes);
      PRINTF("That pointer points to %p\r\n", *elfloader_autostart_processes);
      add_components((struct component * const *) elfloader_autostart_processes);
      componentgroups[slot] = (struct component **) elfloader_autostart_processes;
      PRINTF("Added components found in file %s\r\n", filename);
    } else {
      PRINTF("Error %u loading components from file %s\r\n", ret, filename);
      PRINTF("Symbol not found: %s\r\n", elfloader_unknown);
      // Release allocated slot
      elfloader_unload(slot);
      slot = ELFLOADER_SLOTS; // this is an invalid slot number.
    }
  } else {
    PRINTF("Could not open file %s to load components\r\n", filename);
  }
  return slot;
}

static u8_t cstore_newdata(struct cstore_buf * buf) {
  if(buf->readlen > 0) {
    // still data left in uip_appdata
    PRINTF("There is still data left in uip_appdata: %u bytes\r\n", buf->readlen);
    return 1;
  } else if (buf->state == CSTORE_BUF_READ) {
    // data is read
    PRINTF("All data was read from uip_appdata, now waiting for new data to come in\r\n");
    buf->state = CSTORE_BUF_BLOCKED_NEWDATA;
    return 0;
  } else if (uip_newdata()) {
    // new data in uip_appdata
    PRINTF("New data available from uip\r\n");
    buf->readptr = uip_appdata;
    buf->readlen = uip_datalen();
    buf->state = CSTORE_BUF_NONE;
    return 1;
  } else {
    // no new data
    PRINTF("No new data available\r\n");
    return 0;
  }
}

static void init_buf(struct cstore_buf * buffer) {
  buffer->readlen = 0;
  buffer->readptr = NULL;
  buffer->state = CSTORE_BUF_NONE;
}

static u16_t read_length(struct cstore_buf * buffer) {
  u16_t * len = (u16_t *) buffer->readptr;
  buffer->state = CSTORE_BUF_READ;
  buffer->readptr += sizeof(u16_t);
  buffer->readlen -= sizeof(u16_t);
  return HTONS(*len); // NTOHS operation is identical to HTONS
}

static void read_component(struct cstore_conn_state * state) {
  // FIXME: infinite loop when failure occurs ?
  int written = cfs_write(state->fp, state->buffer.readptr, state->buffer.readlen);
  state->buffer.state = CSTORE_BUF_READ;
  state->buffer.readptr += written;
  state->buffer.readlen -= written;
  state->received += written;
  PRINTF("Written %u bytes to file\r\n", written);
}

static 
PT_THREAD(handle_connection(struct cstore_conn_state * state))
{
    PT_BEGIN(&(state->pt));

    PT_WAIT_UNTIL(&(state->pt), cstore_newdata(&(state->buffer)));

    // We have data, read the length
    state->length = read_length(&(state->buffer)); // TODO: what if we only receive 1 byte in first packet?
    PRINTF("Length of component is %hu\r\n", state->length);

    // Read the component itself
    PRINTF("Waiting for component itself to arrive\r\n");
    // TODO use name.component or something like that ...
#ifdef CONTIKI_TARGET_SKY
    cfs_coffee_reserve(TMP_FILE, state->length);
#endif
    state->fp = cfs_open(TMP_FILE, CFS_WRITE);
    if(state->fp != -1) {
      do {
        PT_WAIT_UNTIL(&(state->pt), cstore_newdata(&(state->buffer)));
        read_component(state);
      } while(state->received < state->length);
      cfs_close(state->fp);
      state->fp = -1;
      PRINTF("Succesfully written component to file\r\n");
    } else {
      PRINTF("Could not open file to write component\r\n");
    }
    
    // load the component
    int slot = cstore_load(TMP_FILE);
   
    // Remove the file
    PRINTF("Removing file %s\r\n", TMP_FILE);
    cfs_remove(TMP_FILE);

    if(slot >= 0 && slot < ELFLOADER_SLOTS) {
      // send info back to clients
      for(state->infoc = componentgroups[slot]; *(state->infoc) != NULL; (state->infoc)++) {
        PRINTF("We have loaded component '%s' with id %u\r\n", COMPONENT_TYPE((*(state->infoc))), (*(state->infoc))->id);
        // Send the id for this component
        do {
          PRINTF("We send %u\r\n", (*state->infoc)->id);
          uip_send(&((*state->infoc)->id),sizeof(u8_t));
          PT_YIELD_UNTIL(&(state->pt), uip_poll() || uip_rexmit());
        } while(!uip_poll());
      }
    }
    // Send a 0 to indicate EOT
    do {
      PRINTF("Sending 0 to indicate EOT\r\n");
      ((char*)uip_appdata)[0] = 0;
      uip_send(uip_appdata, 1);
      PT_YIELD_UNTIL(&(state->pt), uip_poll() || uip_rexmit() || uip_closed());
    } while(!(uip_poll() || uip_closed()));

     // We're done, close connection if needed
    if(!uip_closed()) {
      uip_close();
    }

    PT_END(&(state->pt));
}

void cstore_unload(u8_t cid) {
  // find the slot in which the component resides
  uint8_t slot = ELFLOADER_SLOTS;
  int i, j;
  for(i=0; i < ELFLOADER_SLOTS && slot >= ELFLOADER_SLOTS; ++i) {
    if(componentgroups[i] == NULL) continue;
    for(j=0; componentgroups[i][j] != NULL; ++j) {
      if(componentgroups[i][j]->id == cid) {
        slot = (uint8_t)i;
        break;
      }
    }
  }
  if(slot >= ELFLOADER_SLOTS) {
    // component is not dynamically loaded
    return;
  }
  PRINTF("About to unload components from slot %u\r\n", slot);
  // for each component in the slot:
  for(i=0; componentgroups[slot][i] != NULL; ++i) {
    PRINTF("Unloading component '%s' with id %u\r\n", COMPONENT_TYPE(componentgroups[slot][i]), componentgroups[slot][i]->id);
    // stop the component
    components_stop(componentgroups[slot][i]->id);
    // completely unwire the component
    events_unwire_component(componentgroups[slot][i]->id);
    // remove the component from the component list
    components_remove(componentgroups[slot][i]->id);
    // unload the code in the elfloader
    elfloader_unload(slot);
  }
}

PROCESS_THREAD(looci_component_store, ev, data)
{
  PROCESS_BEGIN();

  memb_init(&states);
  int i;
  for(i=0; i < ELFLOADER_SLOTS; ++i) {
    componentgroups[i] = NULL;
  }

  PRINTF("Component Store starting\r\n");

  tcp_listen(HTONS(LOOCI_CSTORE_PORT));
  
  while(1) {
    PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event && uip_conn->lport == HTONS(LOOCI_CSTORE_PORT));
    if(uip_connected()) {
      PRINTF("LooCI Component Store, connection from rport: %u\r\n", HTONS(uip_conn->rport));
      // Setup the connection
      struct cstore_conn_state * state = memb_alloc(&states);
      if(state != NULL) {
        PT_INIT(&(state->pt));
        init_buf(&(state->buffer));
        state->fp = -1;
        state->received = 0;
        state->infoc = NULL;
        tcp_markconn(uip_conn, state);
        data = state;
      } else {
        uip_abort();
        continue;
      }
    }
    // Schedule the protothread
    if(uip_aborted() || uip_timedout() || (data!=NULL && PT_SCHEDULE(handle_connection(data))==0)) { // data!=NULL: sanity check
      // Protothread exited or connection closed from other end -> cleanup
      // TODO: in which order are closed, aborted, etc generated ?
      PRINTF("Connection has been aborted, timed out or handling the connection is finished\r\n");
      if(((struct cstore_conn_state *)data)->fp != -1) {
        cfs_close(((struct cstore_conn_state *)data)->fp);
      }
      tcp_markconn(uip_conn, NULL);
      memb_free(&states, data);
    }
  }

  PROCESS_END();
}

/** @} */
